package com.talkyun.openx.error;

import com.talkyun.utils.Reflector;
import com.talkyun.utils.json.JSON;
import com.talkyun.utils.json.JSONObject;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Handler any exception, from service to client
 * <p/>
 * Created by kevin on 2016/11/16
 */
public class ExceptionHandler {

    public String encode(Throwable t) {
        try {
            return toJson(t);
        } catch (Throwable e) {
            JSONObject json = JSON.parseObject("{}");
            json.put("code", "0000" + "0000");
            json.put("class", e.getClass().getName());
            json.put("message", e.getMessage());
            return json.toString();
        }
    }

    public Exception decode(final String text) {
        try {
            return fromJson(text);
        } catch (Throwable t) {
            // safe guard
            return new RuntimeException(text);
        }
    }

    private Exception fromJson(final String text) throws Throwable {
        final JSONObject json = JSON.parseObject(text);

        Class clazz = Class.forName(json.getString("class"));
        if (clazz == RuntimeException.class) {
            //
            return new RuntimeException() {
                @Override
                public String toString() {
                    return json.getString("message");
                }
            };
        }

        Object error = Reflector.create(clazz);
        for (Field field : Reflector.getAllField(clazz)) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            String fieldName = field.getName();
            Object fieldVals = json.get(fieldName);
            if (fieldVals != null) {
                if (field.getType() == Class.class) {
                    field.set(error, Class.forName(fieldVals.toString()));
                } else {
                    field.set(error, fieldVals);
                }
            }
        }

        return (Exception) error;
    }

    private String toJson(Throwable t) throws IllegalAccessException {
        // check self toString is json
        String text = t.toString();
        try {
            JSONObject json = JSON.parseObject(text);
            if (json.exists("code") && json.exists("message")) {
                return text;
            }
        } catch (Throwable tx) {
            // ignore
        }

        // general exception handle
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("class", t.getClass().getName());
        if (t instanceof RuntimeException) {
            map.put("message", t.toString());
        } else {
            for (Field field : Reflector.getAllField(t.getClass())) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                String name = field.getName();
                Object val = field.get(t);
                if (val != null) {
                    this.handle(map, name, val);
                }
            }
        }
        return JSON.toJsonString(map);
    }

    private void handle(Map<String, Object> map, String name, Object val) {
        if (val.getClass().isPrimitive()) {
            map.put(name, val);
        } else if (val instanceof Number) {
            map.put(name, val);
        } else if (val instanceof Character) {
            map.put(name, val);
        } else if (val instanceof Boolean) {
            map.put(name, val);
        } else if (val instanceof String) {
            map.put(name, val);
        } else if (val instanceof Date) {
            map.put(name, val);
        } else if (val instanceof Enum) {
            map.put(name, val);
        } else if (val instanceof Class) {
            map.put(name, val);
        }
    }
}